﻿// Inner Fire 游戏引擎库
// DirectX 数学工具类
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 把imath分离出来，不要每次core改动都编译。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-03-13

#pragma once

#include <DirectXMath.h>
#include <assimp/matrix4x4.h>
#include <assimp/quaternion.h>
#include <assimp/vector3.h>
#include <graphics/graphics_def.h>

namespace ifire {

/// <summary>
/// 用于DirectX的数学类。处理计算相关的工具。
/// </summary>
class imath {

public:
  // 无限
  static const float Infinity;

public:
  // 将角度转化为弧度

  // 返回float随机数在：[0, 1) 范围
  static float RandF() { return (float)(rand()) / (float)RAND_MAX; }

  // 返回float随机数在：[a, b) 范围
  static float RandF(float a, float b) { return a + RandF() * (b - a); }

  // 返回int随机数在：[a, b] 范围
  static int Rand(int a, int b) { return a + rand() % ((b - a) + 1); }

  // 交换
  template <typename T> static void Swap(T& a, T& b) {
    T temp = b;
    b = a;
    a = temp;
  }

  // 返回两者中的最小数
  template <typename T> static T Min(const T& a, const T& b) {
    return a < b ? a : b;
  }

  // 返回两者中的最大数
  template <typename T> static T Max(const T& a, const T& b) {
    return a > b ? a : b;
  }

  // 进行一个线程插值
  template <typename T> static T Lerp(const T& a, const T& b, float t) {
    return a + (b - a) * t;
  }

  // 进行一个范围限制操作。
  template <typename T>
  static T Clamp(const T& x, const T& low, const T& high) {
    return x < low ? low : (x > high ? high : x);
  }

  // 返回点 (x,y) 的极角，其值在 [0, 2π) 范围内。
  static float AngleFromXY(float x, float y);
  // 缩放一个向量（与标量乘法）
  inline static void Scale(IVec3& vec, float value) {
    vec.x *= value;
    vec.y *= value;
    vec.z *= value;
  }

  // 判断项目是否为0
  inline static bool IsNotZero(IVec3 vec) {
    return fabs(vec.x) > F_EPSILON || fabs(vec.y) > F_EPSILON ||
           fabs(vec.z) > F_EPSILON;
  }
  inline static bool IsZero(IVec3 vec) {
    return fabs(vec.x) < F_EPSILON && fabs(vec.y) < F_EPSILON &&
           fabs(vec.z) < F_EPSILON;
  }

  // 得到用于旋转法线的“逆->转置”矩阵。
  static IMat InverseTranspose(FMat M) {
    IMat A = M;
    // 将平移行置零，以确保它不会进入我们的逆-转置计算——我们不想要平移的逆-转置。
    A.r[3] = DirectX::XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f);

    IVec det = DirectX::XMMatrixDeterminant(A);
    return DirectX::XMMatrixTranspose(DirectX::XMMatrixInverse(&det, A));
  }

  // 得到一个单位矩阵
  static IMat4 Identity4x4() {
    static IMat4 I(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
        1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
    return I;
  }

  // 得到一个0矩阵
  static IMat4 Zero4x4() {
    static IMat4 I(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
    return I;
  }

  // 计算三角型的平面法线
  static IVec ComputeNormal(FVec p0, FVec p1, FVec p2) {
    auto u = DirectX::XMVectorSubtract(p1, p0);
    auto v = DirectX::XMVectorSubtract(p2, p0);
    return DirectX::XMVector3Normalize(DirectX::XMVector3Cross(u, v));
  }

  // 得到一个随机的单位向量
  static IVec RandUnitVec3();
  // 得到一个随机的半球单位向量
  static IVec RandHemisphereUnitVec3(IVec n);
  // 将一个球面坐标系变为笛卡尔坐标系
  static IVec SphericalToCartesian(float radius, float theta, float phi);
  // 将一个[0,1]范围的值，转化为NDC空间的[-1,1]
  static IVec2 PercentToNDC(const IVec2 value);
  // 将一个Win32窗口（比如1920x1080)的值，转化为NDC空间的值
  static IVec2 ConvertToNDC(const IVec2 position, const IVec2 client_size);

  // 将aiMatrix4x4转换为DirectX矩阵
  static IMat4 ConvertMatrix(const aiMatrix4x4& mat);
  // 将aiMatrix4x4转换为DirectX矩阵，并且附带单位的转换
  static IMat4 ConvertUnitsMatrix(const aiMatrix4x4& mat);
  // 将aiMatrix4x4转换为Dx，而且附带从右手坐标系转为左手坐标系。
  static IMat4 ConvertMatrixLH(const aiMatrix4x4& mat);

  // 将四元数转换为欧拉角（单位：弧度）
  static IVec3 QuaternionToEuler(FVec q);
  // 将四元数转换为欧拉角（单位：角度）
  static IVec3 QuaternionToEulerDegree(FVec q);
  // 将四元数转换为欧拉角（单位：角度）
  static IVec3 QuaternionToEulerDegree(IVec4 q);
  // 将欧拉角（单位为角度）转化为四元数
  static IVec4 EulerToQuaternion(IVec3 eular_angles);
  // 将欧拉角（单位为角度）转化为四元数
  static IVec EulerToQuaternion(FVec eular_angles);

  // 计算两个项目之间
  static float CalculateBoneLength(
      FMat parent_bone, FMat current_bone) noexcept;

  // 得到一个逆矩阵
  inline static IMat Inverse(FMat mat) {
    auto det = XMMatrixDeterminant(mat);
    return XMMatrixInverse(&det, mat);
  }

  // 计算从 q_a 到 q_b 的旋转差值 q_c
  inline static IVec QuaternionDifference(IVec q_a, IVec q_b) {
    IVec q_a_inv = DirectX::XMQuaternionInverse(q_a);
    return DirectX::XMQuaternionMultiply(q_b, q_a_inv);
  }

  // 将一个矩阵逆向解析为平移、旋转，缩放向量
  static IVec3 TranslationFromMatrix(FMat mat);
  static IVec3 RotationFromMatrix(FMat mat);
  static IVec3 ScalingFromMatrix(FMat mat);

  inline static IVec3 ConvertVector3(const aiVector3D& v) {
    return IVec3(v.x, v.y, v.z);
  }
  inline static IVec4 ConvertQuaternion(const aiQuaternion& v) {
    return IVec4(v.x, v.y, v.z, v.w);
  }

  inline static float len(FVec vec) {
    return DirectX::XMVectorGetX(DirectX::XMVector3Length(vec));
  }

  inline static float len_sq(FVec vec) {
    return DirectX::XMVectorGetX(DirectX::XMVector3LengthSq(vec));
  }

  inline static float len_est(FVec vec) {
    return DirectX::XMVectorGetX(DirectX::XMVector3LengthEst(vec));
  }

  inline static IVec normalize(FVec vec) {
    return DirectX::XMVector3Normalize(vec);
  }
  inline static IVec normalize_est(FVec vec) {
    return DirectX::XMVector3NormalizeEst(vec);
  }

  // 通过一个正弦波来生成山地。这是得到指定位置的高度。
  static float GetHillsHeight(float x, float z);
  // 通过一个正弦波来生成山地。这是得到指定位置的法线。
  static IVec3 GetHillsNormal(float x, float z);

  // 根据向前的向量计算旋转方向(up默认向上）
  static IVec QuaternionLookTo(
      FVec forward, FVec up = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));

  // 对两个矩阵进行线性插值（将项目分解之后再进行插件）
  static IMat MatrixLerp(FMat m1, FMat m2, float t);

  // 计算A矩阵到B矩阵的变换矩阵C。即符合A * C = B数学运算。
  inline static IMat MatAToB(FMat a, FMat b) {
    return DirectX::XMMatrixMultiply(
        DirectX::XMMatrixInverse(nullptr, a), b); // 返回 A⁻¹ * B
  }
  // 矩阵减法，A-B应理解为​从B到A的相对变换，即解方程B*C=A得到的C。
  // 这个MatSubtract是不能和CalcAToB对换的，A和B怎么调换，不能达到CalcAToB的效果。
  inline static IMat MatSubtract(FMat a, FMat b) {
    return DirectX::XMMatrixMultiply(
        a, DirectX::XMMatrixInverse(nullptr, b)); // 返回 A * B⁻¹
  }
  // 矩阵加法，A+B=C，即A*B=C
  inline static IMat MatAdd(FMat a, FMat b) {
    return DirectX::XMMatrixMultiply(a, b);
  }

  // 从一个矩阵中提取出平移分量
  inline static IVec PickTranslate(FMat m) noexcept {
    // TIPS：这里遇到一个奇怪的BUG，如果我在这个函数前面使用XM_CALLCONV，
    // 这会导致这个读取方式出现错误，无法得到应该有的值。这应该是和SIMD的优化有关。以后要注意。
    // 我使用using FMat = FXMMATRIX。这东西好像无法得到应该有的优化，是无效的。
    // const __m128 mask = _mm_set_ps(0, -1, -1, -1); // [0,1,1,1]掩码
    // return _mm_and_ps(m.r[3], mask);
    return DirectX::XMVectorSet(
        m.r[3].m128_f32[0], m.r[3].m128_f32[1], m.r[3].m128_f32[2], 0.0f);
  }

  // 这里只重置Z的移动。因为如果重置XY，会导致模型晃动。
  // 目前这个运行很好，但我不知道会不会有更深层的问题。
  inline static void RemoveTranslate(IMat& m) { m.r[3].m128_f32[2] = 0.0f; }

  // 从一个矩阵中提取旋转四元数
  inline static IVec PickRotation(FMat M) noexcept {
    IVec scale, rotationQuat, translation;
    DirectX::XMMatrixDecompose(&scale, &rotationQuat, &translation, M);
    return rotationQuat;
  }

  // 通过一个新的四元数，替换原矩阵中的旋转（比重构矩阵更高效）
  static IMat SetRotation(FMat mat, FQuat quat) noexcept;

  // 替换一个矩阵中的平移分量
  inline static void SetTranslate(IMat& mat, IVec pos) noexcept {
    // g_XMSelect1110是预定义的常量(0,0,0,1)掩码，表示保留新向量的XYZ，保留原矩阵的W分量
    mat.r[3] = DirectX::XMVectorSelect(mat.r[3], //
        pos, DirectX::g_XMSelect1110);
  }

  // 将一个矩阵中的平移分量设置到另外一个矩阵。
  static void ReplaceTranslation(IMat& a, const IMat& b) {
    // 1. 提取b的平移向量(第4列的前3个分量)
    IVec bTranslation = b.r[3];
    // 2. 保持a的旋转/缩放部分(前3行3列)，仅替换平移部分
    a.r[3] = DirectX::XMVectorSelect(a.r[3], bTranslation, //
        DirectX::XMVectorSelectControl(1, 1, 1, 0));
  }

  // 通过Position，Rotation构建变换矩阵，默认的Scale为1.0
  inline static IMat BuildMat(IVec position, IQuat rotation) {
    return DirectX::XMMatrixAffineTransformation(
        SCALE1, VEC0, rotation, position);
  }
  // 通过Position, Rotation, Scale构建变换矩阵
  inline static IMat BuildMat(IVec position, IQuat rotation, IVec scale) {
    return DirectX::XMMatrixAffineTransformation(
        scale, VEC0, rotation, position);
  }
  // 构建平移矩阵
  inline static IMat TranslationMat(IVec position) {
    return DirectX::XMMatrixTranslationFromVector(position);
  }
  inline static IMat TranslationMat(float x, float y, float z) {
    return DirectX::XMMatrixTranslation(x, y, z);
  }
  // 通过一个四元数构造旋转矩阵
  inline static IMat RotationMat(IVec rotation) {
    return DirectX::XMMatrixRotationQuaternion(rotation);
  }
  // 通过Pitch，Yaw，Roll构建旋转矩阵
  inline static IMat RotationMat(float x, float y, float z) {
    return DirectX::XMMatrixRotationRollPitchYaw(x, y, z);
  }
  // 构建缩放矩阵
  inline static IMat ScaleMat(IVec rotation) {
    return DirectX::XMMatrixScalingFromVector(rotation);
  }
  // 构建缩放矩阵
  inline static IMat ScaleMat(float x, float y, float z) {
    return DirectX::XMMatrixScaling(x, y, z);
  }

  // 函数：仅缩放矩阵的平移分量
  static IMat XM_CALLCONV ScaleTranslation(
      DirectX::FXMMATRIX mat, DirectX::FXMVECTOR scale) noexcept;

  // 反转Scale
  inline static IVec XM_CALLCONV InverseScale(DirectX::FXMVECTOR scaledVector,
      DirectX::FXMVECTOR scaleFactors) noexcept {
    return _mm_div_ps(scaledVector, scaleFactors); // SSE直接除法
  }

  // 限制一个向量的长度
  inline static void ClampLimit(IVec& vec, float limit) {
    float current_linear = DirectX::XMVectorGetX(DirectX::XMVector3Length(vec));
    if (current_linear > limit) {
      vec = DirectX::XMVectorMultiply(
          DirectX::XMVector3Normalize(vec), DirectX::XMVectorReplicate(limit));
    }
  }

  static IVec CalculateDirection(FMat A, FMat B);

  /// <summary>
  /// 对骨骼的变换进行修正
  /// </summary>
  /// <param name="parent_mat">动画状态下父骨骼的世界变换矩阵</param>
  /// <param name="child_mat">动画状态下子骨骼的世界变换矩阵</param>
  /// <param name="parent_pose">T-pose下父骨骼的世界变换矩阵</param>
  /// <param name="child_pose">T-pose下子骨骼的世界变换矩阵</param>
  /// <returns></returns>
  static IVec CalculateCorrectedRotation(
      FMat parent_mat, FMat child_mat, FMat parent_pose, FMat child_pose);

  /// <summary>
  /// 对骨骼的变换进行修正。
  /// TODO:这个算法是我从Humanoid骨骼中计算得来的。但是实际上好像还是会有点问题
  /// 头发有时候会歪。我原来的Humanoid有的时候骨骼也歪的，可能存在算法上的问题。
  /// 以后有时间再检查。
  /// </summary>
  /// <param name="mat">父骨骼的矩阵。因为只有从子级向父级计算</param>
  /// <param name="parent_origin">父级在t-pose（或变换前）的位置</param>
  /// <param name="bone_origin">子级在t-pose（或变换前）的位置</param>
  /// <param name="parent_current">父级在动画（或变换后）的位置</param>
  /// <param name="bone_current">子级在动画（或变换后）的位置</param>
  /// <returns>用于替换的新旋转矩阵</returns>
  static IVec GetBoneRotationFix(FMat mat, IVec parent_origin, IVec bone_origin,
      IVec parent_current, IVec bone_current);
  // 这个是能够防止除0错误的版本
  static IVec GetBoneRotationFixSafe(FMat mat, IVec parent_origin,
      IVec bone_origin, IVec parent_current, IVec bone_current);
};

} // namespace ifire